﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;

using System.IO;
using System.IO.Ports;
using System.Reflection;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
using System.Data.SQLite;

namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        static SerialPort serial;
        public static SQLiteConnection db;
        byte[] buffer = new byte[256];
        int buffersz = 0;
        int pktn = 0;

        string[] comandos = new string[] { "PING", "PONG", "QUERY", "ANSWER", "ANSWERNO" };

        public Form1()
        {
            InitializeComponent();
            serial = new SerialPort();
            serial.DataReceived += new SerialDataReceivedEventHandler(serial_DataReceived);
            EnableControls();
            db = new SQLiteConnection(@"Data Source=C:\Users\netbook\Desktop\acesso\db.s3db");
            try
            {
                db.Open();
            }
            catch (SQLiteException e)
            {
                MessageBox.Show(e.ToString(), "Não conseguiu abrir o banco de dados!");
            }
        }

        private void Form1_Shown(object sender, EventArgs e)
        {
            cbCom.Items.Clear();
            foreach (string s in SerialPort.GetPortNames())
            {
                cbCom.Items.Add(s);
            }
            cbCom.SelectedIndex = 0;

            rdLog_Click(null, null);

        }

        private void EnableControls()
        {
            if (serial.IsOpen)
            {
                btConectar.Text = "&Desconectar";
                cbCom.Enabled = false;
                buffersz=0;
            }
            else
            {
                btConectar.Text = "&Conectar";
                cbCom.Enabled = true;
            }
        }

        private void btConectar_Click(object sender, EventArgs e)
        {
            bool error = false;
            if (serial.IsOpen) serial.Close();
            else
            {
                serial.PortName = cbCom.Text;
                serial.BaudRate = 9600;
                serial.Parity = Parity.None;
                serial.DataBits = 8;
                serial.StopBits = StopBits.One;
                serial.Handshake = Handshake.None;
                //serial.RtsEnable = false;
                //serial.ReadTimeout = 500;
                //serial.WriteTimeout = 500;
                try
                {
                    // Open the port
                    serial.Open();
                }
                catch (UnauthorizedAccessException) { error = true; }
                catch (IOException) { error = true; }
                catch (ArgumentException) { error = true; }

                if (error) MessageBox.Show(this, "Não conseguiu abrir a porta.", "COM Port Unavalible", MessageBoxButtons.OK, MessageBoxIcon.Stop);
                
                //serial.SetRtsControlToggle();

            }
            EnableControls();
        }

        private void serialSend(int to, int command, string data)
        {
            if (pktn < 255) pktn++; else pktn = 0;
            serialSend(to, command, pktn, data);
        }

        private void serialSend(int to, int command, int pkt, string data)
        {
            if (!serial.IsOpen) return;
            int i;

            byte[] sb = new byte[7+data.Length];
            sb[0] = 0x24;
            sb[1] = (byte)pkt;
            sb[2] = (byte)(data.Length+1);
            sb[3] = 0x00;
            sb[4] = (byte)to;
            sb[5] = (byte)command;

            for (i = 0; i < data.Length; ++i) sb[i + 6] = (byte)data[i];

            byte c = 0x00;
            for (i = 0; i < (6 + data.Length); ++i) c ^= sb[i];
            sb[6 + data.Length] = c;

            for (i = 0; i < (7 + data.Length); ++i)
            {
                txtlog_invoke(String.Format("{0:X2} ", (int)sb[i]));
            }
            txtlog_invoke(Environment.NewLine);

            //txtlog_invoke(String.Format("Aguardando {0} ms",(int)Math.Ceiling((double)1.06 * (double)((7 + data.Length)*10))));
            //txtlog_invoke(Environment.NewLine);

            //serial.RtsEnable = true;
            //Thread.Sleep(10);
            serial.Write(sb,0,7+data.Length);
            //Thread.Sleep((int)Math.Ceiling((double)1.06 * (double)((7 + data.Length) * 8)));
            //serial.RtsEnable = false;
        }


        private void serial_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            if (!serial.IsOpen) return;
            int datasz = serial.BytesToRead;
            if (datasz > 255) datasz = 255;
            byte[] data = new byte[256];
            serial.Read(data, 0, datasz);
            txtlog_invoke(String.Format("Recebeu {0} bytes"+Environment.NewLine, datasz));
            processdata(data, datasz);
        }

        private void processdata(byte[] data, int datasz)
        {
            int fromterminal = 0;
            int toterminal = 0;
            int msglen = 0;
            int comando = 0;
            int rpktn = 0;
            int i;
        
            if (buffersz > 0)
            {
                if (data[0] != '$')
                {
                    Array.Copy(data, 0, buffer, buffersz, datasz);
                    buffersz += datasz;
                    Array.Copy(buffer, 0, data, 0, buffersz);
                    datasz = buffersz;
                }
                else
                    txtlog_invoke(String.Format("{0} Mensagem anterior descartada({1})\r\n", DateTime.Now, buffersz));

                buffersz = 0;
            }

            txtlog_invoke("Len: "+datasz+Environment.NewLine);
            for (i = 0; i < datasz; ++i)
            {
                txtlog_invoke(String.Format("{0:X2} ", (int)data[i]));
            }
            txtlog_invoke(Environment.NewLine);

            if (datasz > 1) rpktn = (int)data[1];
            if (datasz > 2) msglen = (int)data[2];
            if (datasz > 3) fromterminal = (int)data[3];
            if (datasz > 4) toterminal = (int)data[4];
            if (datasz > 5) comando = (int)data[5];

    
            if (datasz>4 && (msglen+6)<=datasz) // se a msg tiver completa
            {
                //calcula checksum
                byte c=0;
                for (i = 0; i < datasz; ++i)
                    c ^= data[i];

                if (c != 0)
                {
                    txtlog_invoke("Checksum incorreto" + Environment.NewLine);
                    return;
                }

                txtlog_invoke(String.Format("{0} - {1}->{2} {3} ({4})", DateTime.Now, fromterminal, toterminal, comandos[comando], msglen) + Environment.NewLine);

                if (toterminal == 0)
                {
                    if (comando != 1) // se nao for pong
                    {
                        // confirma o recebimento do pacote com pong
                        Thread.Sleep(10);
                        serialSend(fromterminal, 0x01, rpktn, "");
                    }
                    
                    if (comando == 0) // ping
                    {
                        txtlog_invoke(String.Format("{0} - {1}->{2} PING", DateTime.Now, toterminal, fromterminal) + Environment.NewLine);
                    }
                    else if (comando == 2) // tag rfid request
                    {
                        string sql = String.Format("SELECT msg FROM rfid WHERE id ='{0}{1}{2}{3}{4}{5}{6}{7}' AND ativo=1", (char)data[6], (char)data[7], (char)data[8], (char)data[9], (char)data[10], (char)data[11], (char)data[12], (char)data[13]);

                        txtlog_invoke(sql+Environment.NewLine);

                        SQLiteCommand command = new SQLiteCommand(sql, db);

                        try
                        {
                            SQLiteDataReader reader = command.ExecuteReader();
                            if (reader.HasRows)
                            {
                                int count = 0;
                                string response = "";
                                while (reader.Read())
                                {
                                    count++;
                                    if (count > 1) break;
                                    response = reader["msg"].ToString();
                                    //txtlog_invoke(String.Format("{0} {1}",response,count)+Environment.NewLine);
                                }

                                if (count > 1) response += "+";

                                serialSend(fromterminal, 0x03, response);
                                
                                command.Dispose();
                                command = new SQLiteCommand(String.Format("INSERT INTO log(rfid,terminal,data) VALUES ('{0}{1}{2}{3}{4}{5}{6}{7}',{8},strftime('%s', 'now'))", (char)data[6], (char)data[7], (char)data[8], (char)data[9], (char)data[10], (char)data[11], (char)data[12], (char)data[13],fromterminal), db);
                                command.ExecuteNonQuery();

                                txtlog_invoke(String.Format("{0} - {1}->{2} ANSWER: {3}({4})", DateTime.Now, toterminal, fromterminal, response, response.Length) + Environment.NewLine);
                            }
                            else
                            {
                                serialSend(fromterminal, 0x04, "TAG NAO         CADASTRADA");
                                txtlog_invoke(String.Format("{0} - {1}->{2} ANSWERNO: Tag nao cadastrada", DateTime.Now, toterminal, fromterminal) + Environment.NewLine);
                            }

                        }
                        catch
                        {
                            txtlog_invoke("Problema no SQL" + Environment.NewLine + sql + Environment.NewLine);
                        }

                    }
                }
            }
            else if (msglen + 6 > datasz || datasz<3) // guarda a mensagem no buffer
            {
                Array.Copy(data, 0, buffer, 0, datasz);
                buffersz = datasz;
            }
            else
            {
                txtlog_invoke("Recebidos " + datasz + " (" + (int)msglen + ") bytes de dados invalidos" + Environment.NewLine);
                for(i=0;i<datasz;++i)
                {
                    txtlog_invoke(String.Format("{0:X2} ", (int)data[i]));
                }
                txtlog_invoke(Environment.NewLine);
            }


        }

        private void txtlog_DoubleClick(object sender, EventArgs e)
        {
            txtlog.Clear();
        }

        private void txtlog_invoke(string add)
        {
                txtlog.Invoke(new EventHandler(delegate
                {
                    txtlog.Text += add;
                }));
                txtlog.Invoke(new EventHandler(delegate
                {
                    txtlog.SelectionStart = txtlog.Text.Length;
                }));
                txtlog.Invoke(new EventHandler(delegate
                {
                    txtlog.ScrollToCaret();
                }));
                txtlog.Invoke(new EventHandler(delegate
                {
                    //txtlog.Refresh();
                }));
        }


        private void pingN_KeyUp(object sender, KeyEventArgs e)
        {
            //txtlog.Text += String.Format("{0} - 0->{1} PING", DateTime.Now, pingN.Value) + Environment.NewLine;
            //serialSend((int)pingN.Value, 0x00, "");
        }

        private void dbLog_DoubleClick(object sender, EventArgs e)
        {
            //MessageBox.Show(dbLog.Rows[dbLog.CurrentRow.Index].Cells["id"].Value.ToString());
            Form2 frm = new Form2();
            frm.edita(dbLog.Rows[dbLog.CurrentRow.Index].Cells["id"].Value.ToString());
        }

        private void txtSearch_KeyPress(object sender, KeyPressEventArgs e)
        {
            if (e.KeyChar == (char)13)
            {
                string sqlquery="";
                if (rdApto.Checked)
                    sqlquery = "SELECT id,apto as Apartamento,msg as Mensagem FROM rfid WHERE apto=" + txtSearch.Text;
                else if (rdId.Checked)
                    sqlquery = "SELECT id,apto as Apartamento,msg as Mensagem FROM rfid WHERE id='" + txtSearch.Text+"'";

                SQLiteCommand command = new SQLiteCommand(sqlquery, db);
                SQLiteDataAdapter da = new SQLiteDataAdapter(command);
                DataSet ds = new DataSet();

                da.Fill(ds);
                DataTable dt = ds.Tables[0];
                dbLog.DataSource = dt;
            }
        }

        private void rdLog_CheckedChanged(object sender, EventArgs e)
        {
            if (rdLog.Checked) txtSearch.Enabled = false; else txtSearch.Enabled = true;
        }

        private void rdLog_Click(object sender, EventArgs e)
        {
            SQLiteCommand command = new SQLiteCommand("SELECT rfid as id ,terminal,datetime(data, 'unixepoch', 'localtime') AS Data FROM log ORDER BY data DESC", db);
            SQLiteDataAdapter da = new SQLiteDataAdapter(command);
            DataSet ds = new DataSet();

            da.Fill(ds);
            DataTable dt = ds.Tables[0];
            dbLog.DataSource = dt;
        }

        private void btNovo_Click(object sender, EventArgs e)
        {
            Form2 frm = new Form2();
            frm.cria();
        }
    }
}

#region serialportex
public class SerialPortEx : SerialPort
{
    public SerialPortEx()
        : base()
    {
    }
    public SerialPortEx(string portName)
        : base(portName)
    {
    }
    public SerialPortEx(IContainer container)
        : base(container)
    {
    }
    public SerialPortEx(string portName, int baudRate)
        : base(portName, baudRate)
    {
    }
    public SerialPortEx(string portName, int baudRate, Parity parity)
        : base(portName, baudRate, parity)
    {
    }
    public SerialPortEx(string portName, int baudRate, Parity parity, int dataBits)
        : base(portName, baudRate, parity, dataBits)
    {
    }
    public SerialPortEx(string portName, int baudRate, Parity parity, int dataBits, StopBits stopBits)
        : base(portName, baudRate, parity, dataBits, stopBits)
    {
    }

    public void SetRtsControlToggle()
    {
        if (BaseStream == null)
            throw new InvalidOperationException("Cannot set RTS_CONTROL_TOGGLE until after the port has been opened.");
        SetDcbFlag(12, 3); // flag 12 is fRtsControl, value 3 is RTS_CONTROL_TOGGLE
        RtsControlToggleSet = true;
    }

    public void ClearRtsControlToggle()
    {
        if (BaseStream != null && RtsControlToggleSet)
        {
            SetDcbFlag(12, 0); // flag 12 is fRtsControl, value 0 is RTS_CONTROL_DISABLE
            RtsControlToggleSet = false;
        }
    }


    private bool RtsControlToggleSet = false;

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool SetCommState(SafeFileHandle hFile, IntPtr lpDCB);

    private void SetDcbFlag(int flag, int value)
    {
        // Get the base stream and its type
        object baseStream = BaseStream;
        Type baseStreamType = baseStream.GetType();

        // Record current DCB Flag value so we can restore it on failure
        object originalDcbFlag = baseStreamType.GetMethod("GetDcbFlag", BindingFlags.NonPublic | BindingFlags.Instance)
          .Invoke(baseStream, new object[] { flag });

        // Invoke the private method SetDcbFlag to change flag to value
        baseStreamType.GetMethod("SetDcbFlag", BindingFlags.NonPublic | BindingFlags.Instance)
          .Invoke(baseStream, new object[] { flag, value });

        try
        {
            // Get the Win32 file handle for the port
            SafeFileHandle _handle = (SafeFileHandle)baseStreamType.GetField("_handle", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(baseStream);

            // Box the private DCB field
            object dcb = baseStreamType.GetField("dcb", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(baseStream);

            // Create unmanaged memory to copy dcb field into
            IntPtr hGlobal = Marshal.AllocHGlobal(Marshal.SizeOf(dcb));
            try
            {
                // Copy dcb field to unmanaged memory
                Marshal.StructureToPtr(dcb, hGlobal, false);

                // Call SetCommState
                if (!SetCommState(_handle, hGlobal))
                    throw new Win32Exception(Marshal.GetLastWin32Error());
            }
            finally
            {
                // Free the unmanaged memory
                Marshal.FreeHGlobal(hGlobal);
            }
        }
        catch
        {
            // Restore original DCB Flag value if we failed to update the device
            baseStreamType.GetMethod("SetDcbFlag", BindingFlags.NonPublic | BindingFlags.Instance)
              .Invoke(baseStream, new object[] { flag, originalDcbFlag });
            throw;
        }
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
            ClearRtsControlToggle();

        base.Dispose(disposing);
    }
}
#endregion
